package org.biubiu0002.baiblog.core.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.biubiu0002.baiblog.common.constants.CacheConstants;
import org.biubiu0002.baiblog.common.constants.FriendshipConstants;
import org.biubiu0002.baiblog.common.exception.BaiBlogException;
import org.biubiu0002.baiblog.common.exception.BizCodeEnumException;
import org.biubiu0002.baiblog.common.utils.PageUtils;
import org.biubiu0002.baiblog.core.async.event.FollowEvent;
import org.biubiu0002.baiblog.core.component.RabbitMqService;
import org.biubiu0002.baiblog.core.dao.FriendshipDao;
import org.biubiu0002.baiblog.core.model.dto.TokenUserDto;
import org.biubiu0002.baiblog.core.model.entity.FriendshipEntity;
import org.biubiu0002.baiblog.core.model.entity.SysUserEntity;
import org.biubiu0002.baiblog.core.model.vo.UserInfoVo;
import org.biubiu0002.baiblog.core.model.vo.req.FollowReqVo;
import org.biubiu0002.baiblog.core.service.FriendshipService;
import org.biubiu0002.baiblog.core.service.SysUserService;
import org.biubiu0002.baiblog.core.utils.IdGenerator;
import org.biubiu0002.baiblog.core.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Author: biubiu0002
 * @Date: 2022/8/1 10:58
 **/
@Service
public class FriendshipServiceImpl extends ServiceImpl<FriendshipDao, FriendshipEntity> implements FriendshipService {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Resource
    private FriendshipDao friendshipDao;

    @Resource
    private SysUserService sysUserService;

    @Resource
    private RabbitMqService rabbitMqService;

    /**
     * 读写穿透
     * 读后写
     * 缓存存在，先写db，后写缓存
     * 缓存不存在，直接写db
     *
     * @param followReqVo vo
     * @return
     */
    @Override
    public int follow(FollowReqVo followReqVo) {
        TokenUserDto user = SecurityUtils.getUser();
        if (user == null) {
            throw new BaiBlogException(BizCodeEnumException.AUTHENTICATE_FAIL_EXCEPTION);
        }
        String followUid = followReqVo.getUid();
        Integer isFollow = followReqVo.getIsFollow();
        String uid = user.getUserUid();
        if (StringUtils.equals(uid, followUid)) {
            throw new BaiBlogException(BizCodeEnumException.FOLLOW_SELF_EXCEPTION);
        }
        SysUserEntity followUser = sysUserService.getUserByUserUid(followUid);
        if (followUser == null) {
            throw new BaiBlogException(BizCodeEnumException.USER_NOT_EXIST_EXCEPTION);
        }
        checkFriendshipRedisKeyExists(uid);
        checkFriendshipRedisKeyExists(followUid);
        //关注人数上限
        Long followSize = redisTemplate.opsForZSet().size(CacheConstants.getUserFollowListKey(uid));
        if (followSize == null) {
            throw new BaiBlogException(BizCodeEnumException.REDIS_NETWORK_ERROR);
        }
        if (followSize >= FriendshipConstants.FOLLOW_COUNT_LIMIT) {
            throw new BaiBlogException(BizCodeEnumException.MAX_FOLLOW_COUNT_EXCEPTION);
        }

        FriendshipEntity friendshipEntity = new FriendshipEntity();
        friendshipEntity.setId(IdGenerator.generateId());
        friendshipEntity.setUserUid(uid);
        friendshipEntity.setFollowUid(followUid);
        friendshipEntity.setFollowed(isFollow);
        friendshipDao.follow(friendshipEntity);
        Long fansCount = 0L;
        if (isFollow == 1) {
            //关注操作
            long curTime = System.currentTimeMillis();
            redisTemplate.opsForZSet().add(CacheConstants.getUserFollowListKey(uid), followUid, curTime);
            redisTemplate.opsForZSet().add(CacheConstants.getUserFansListKey(followUid), uid, curTime);
            fansCount = redisTemplate.opsForZSet().size(CacheConstants.getUserFansListKey(followUid));
        } else if (isFollow == 0) {
            //取关操作
            redisTemplate.opsForZSet().remove(CacheConstants.getUserFollowListKey(uid), followUid);
            redisTemplate.opsForZSet().remove(CacheConstants.getUserFansListKey(followUid), uid);
            fansCount = redisTemplate.opsForZSet().size(CacheConstants.getUserFansListKey(followUid));
        }
        if (fansCount == null) {
            throw new BaiBlogException(BizCodeEnumException.REDIS_NETWORK_ERROR);
        }
        FollowEvent followEvent = new FollowEvent(uid, followUid, isFollow);
        rabbitMqService.sendFollowEvent(followEvent);
        return fansCount.intValue();
    }


    @Override
    public int isFollowing(String targetUid) {
        TokenUserDto user = SecurityUtils.getUser();
        if (user == null) {
            throw new BaiBlogException(BizCodeEnumException.AUTHENTICATE_FAIL_EXCEPTION);
        }
        String uid = user.getUserUid();
        checkFriendshipRedisKeyExists(uid);
        checkFriendshipRedisKeyExists(targetUid);
        Double followed = redisTemplate.opsForZSet().score(CacheConstants.getUserFollowListKey(uid), targetUid);
        Double fan = redisTemplate.opsForZSet().score(CacheConstants.getUserFansListKey(uid), targetUid);
        if (followed != null && fan != null) {
            return FriendshipConstants.FollowEnum.MUTUAL.getValue();
        }
        if (followed != null) {
            return FriendshipConstants.FollowEnum.FOLLOWED.getValue();
        }

        return FriendshipConstants.FollowEnum.NORMAL.getValue();
    }

    @Override
    public int isFollowed(String targetUid) {
        TokenUserDto user = SecurityUtils.getUser();
        if (user == null) {
            throw new BaiBlogException(BizCodeEnumException.AUTHENTICATE_FAIL_EXCEPTION);
        }
        String uid = user.getUserUid();
        checkFriendshipRedisKeyExists(uid);
        checkFriendshipRedisKeyExists(targetUid);
        Double followed = redisTemplate.opsForZSet().score(CacheConstants.getUserFollowListKey(targetUid), uid);
        Double fan = redisTemplate.opsForZSet().score(CacheConstants.getUserFansListKey(targetUid), uid);
        if (followed != null && fan != null) {
            return FriendshipConstants.FollowEnum.MUTUAL.getValue();
        }
        if (followed != null) {
            return FriendshipConstants.FollowEnum.FOLLOWED.getValue();
        }

        return FriendshipConstants.FollowEnum.NORMAL.getValue();
    }


    private void checkFriendshipRedisKeyExists(String uid) {
        Boolean hasUidFollowKey = redisTemplate.hasKey(CacheConstants.getUserFollowListKey(uid));
        Boolean hasUidFansKey = redisTemplate.hasKey(CacheConstants.getUserFansListKey(uid));
        if (!Boolean.TRUE.equals(hasUidFollowKey)) {
            //缓存失效
            this.getFollowList(uid);
        }
        if (!Boolean.TRUE.equals(hasUidFansKey)) {
            //缓存失效
            this.getFansList(uid);
        }
    }

    @Override
    public PageUtils follows(String uid, int page, int limit) {
        String key = CacheConstants.getUserFollowListKey(uid);
        int offset = (page - 1) * limit;
        int upperLimit = offset + limit;
        Boolean hasKey = redisTemplate.hasKey(key);
        if (!Boolean.TRUE.equals(hasKey)) {
            //缓存失效 读数据库 设置缓存
            List<String> followList = this.getFollowList(uid);
            int totalCount = followList.size();
            List<String> strings = followList.subList(Math.min(totalCount, Math.max(0, offset)), Math.max(0, Math.min(followList.size() - 1, upperLimit)));
            if (CollectionUtils.isEmpty(strings)) {
                return new PageUtils(strings, totalCount, limit, page);
            }
            //获取用户详情
            Map<String, UserInfoVo> userInfoMap = sysUserService.getUserInfoMap(strings);
            List<UserInfoVo> collect = strings.stream().map(userInfoMap::get)
                    .filter(Objects::nonNull).collect(Collectors.toList());
            return new PageUtils(collect, followList.size(), limit, page);
        }
        //缓存命中
        Long size = redisTemplate.opsForZSet().size(CacheConstants.getUserFollowListKey(uid));
        if (size == null) {
            throw new BaiBlogException(BizCodeEnumException.REDIS_NETWORK_ERROR);
        }
        Set<String> strings = redisTemplate.opsForZSet().reverseRange(key, offset, upperLimit);
        if (CollectionUtils.isEmpty(strings)) {
            return new PageUtils(Collections.emptyList(), size.intValue(), limit, page);
        }
        ArrayList<String> strings1 = new ArrayList<>(strings);
        Map<String, UserInfoVo> userInfoMap = sysUserService.getUserInfoMap(strings1);
        List<UserInfoVo> collect = strings.stream().map(userInfoMap::get)
                .filter(Objects::nonNull).collect(Collectors.toList());
        return new PageUtils(collect, strings.size(), limit, page);

    }

    @Override
    public List<String> getFollowList(String uid) {
        if (StringUtils.isBlank(uid)) {
            throw new BaiBlogException(BizCodeEnumException.INNER_INPUT_EXCEPTION);
        }
        //1.先获取缓存
        Set<String> strings = redisTemplate.opsForZSet().reverseRange(CacheConstants.getUserFollowListKey(uid), 0, -1);
        //key不存在也不会返回null 会返回空集合
        if (!CollectionUtils.isEmpty(strings)) {
            //缓存命中
            return new ArrayList<>(strings);
        } else {
            //缓存失效
            List<FriendshipEntity> followListFromDB = this.getFollowListFromDB(uid);
            //设置缓存
            Set<ZSetOperations.TypedTuple<String>> collect = followListFromDB.stream()
                    .map(item -> new DefaultTypedTuple<>(item.getFollowUid(), (double) item.getUpdateTime().getTime()))
                    .collect(Collectors.toSet());
            if(!CollectionUtils.isEmpty(collect)){
                redisTemplate.opsForZSet().add(CacheConstants.getUserFollowListKey(uid), collect);
                redisTemplate.expire(CacheConstants.getUserFollowListKey(uid), 3, TimeUnit.DAYS);
            }

            return followListFromDB.stream().map(FriendshipEntity::getFollowUid).collect(Collectors.toList());
        }

    }

    public List<FriendshipEntity> getFollowListFromDB(String uid) {
        if (StringUtils.isBlank(uid)) {
            throw new BaiBlogException(BizCodeEnumException.INNER_INPUT_EXCEPTION);
        }
        List<FriendshipEntity> friendshipEntities = friendshipDao.queryFollowsByUid(uid);
        if (CollectionUtils.isEmpty(friendshipEntities)) {
            return Collections.emptyList();
        }
        return friendshipEntities;
    }

    @Override
    public PageUtils fans(String uid, int page, int limit) {
        String key = CacheConstants.getUserFansListKey(uid);
        int offset = (page - 1) * limit;
        int upperLimit = offset + limit;
        Boolean hasKey = redisTemplate.hasKey(key);
        if (!Boolean.TRUE.equals(hasKey)) {
            //缓存失效 读数据库 设置缓存
            List<String> fansList = this.getFansList(uid);
            int totalCount = fansList.size();
            List<String> strings = fansList.subList(Math.min(fansList.size(), Math.max(0, offset)), Math.max(0, Math.min(fansList.size() - 1, upperLimit)));
            if (CollectionUtils.isEmpty(strings)) {
                return new PageUtils(strings, totalCount, limit, page);
            }
            //获取用户详情
            Map<String, UserInfoVo> userInfoMap = sysUserService.getUserInfoMap(strings);
            List<UserInfoVo> collect = strings.stream().map(userInfoMap::get)
                    .filter(Objects::nonNull).collect(Collectors.toList());
            return new PageUtils(collect, totalCount, limit, page);
        }
        //缓存命中
        Long size = redisTemplate.opsForZSet().size(CacheConstants.getUserFansListKey(uid));
        if (size == null) {
            throw new BaiBlogException(BizCodeEnumException.REDIS_NETWORK_ERROR);
        }
        Set<String> strings = redisTemplate.opsForZSet().reverseRange(key, offset, upperLimit);
        if (CollectionUtils.isEmpty(strings)) {
            return new PageUtils(Collections.emptyList(), size.intValue(), limit, page);
        }
        ArrayList<String> strings1 = new ArrayList<>(strings);
        Map<String, UserInfoVo> userInfoMap = sysUserService.getUserInfoMap(strings1);
        List<UserInfoVo> collect = strings.stream().map(userInfoMap::get)
                .filter(Objects::nonNull).collect(Collectors.toList());
        return new PageUtils(collect, strings.size(), limit, page);
    }

    @Override
    public List<String> getFansList(String uid) {
        if (StringUtils.isBlank(uid)) {
            throw new BaiBlogException(BizCodeEnumException.INNER_INPUT_EXCEPTION);
        }
        Set<String> strings = redisTemplate.opsForZSet().reverseRange(CacheConstants.getUserFansListKey(uid), 0, -1);
        if (!CollectionUtils.isEmpty(strings)) {
            //缓存命中
            return new ArrayList<>(strings);
        } else {
            //缓存失效
            List<FriendshipEntity> fansListFromDB = this.getFansListFromDB(uid);
            Set<ZSetOperations.TypedTuple<String>> tuples = fansListFromDB.stream()
                    .map(item -> new DefaultTypedTuple<>(item.getUserUid(), (double) item.getUpdateTime().getTime()))
                    .collect(Collectors.toSet());
            //设置缓存
            if(!CollectionUtils.isEmpty(tuples)){
                redisTemplate.opsForZSet().add(CacheConstants.getUserFansListKey(uid), tuples);
                redisTemplate.expire(CacheConstants.getUserFansListKey(uid),3, TimeUnit.DAYS);
            }

            return fansListFromDB.stream().map(FriendshipEntity::getUserUid).collect(Collectors.toList());

        }

    }

    public List<FriendshipEntity> getFansListFromDB(String uid) {
        if (StringUtils.isBlank(uid)) {
            throw new BaiBlogException(BizCodeEnumException.INNER_INPUT_EXCEPTION);
        }
        List<FriendshipEntity> friendshipEntities = friendshipDao.queryFansByUid(uid);
        if (CollectionUtils.isEmpty(friendshipEntities)) {
            return Collections.emptyList();
        }
        return friendshipEntities;

    }

    @Override
    public int followCount(String uid) {
        if (StringUtils.isBlank(uid)) {
            throw new BaiBlogException(BizCodeEnumException.INNER_INPUT_EXCEPTION);
        }
        Long size = redisTemplate.opsForZSet().size(CacheConstants.getUserFollowListKey(uid));
        if (size != null) {
            //缓存命中
            return size.intValue();
        } else {
            //缓存失效 包括了设置缓存的逻辑
            return this.getFollowList(uid).size();
        }
    }

    @Override
    public int fansCount(String uid) {
        if (StringUtils.isBlank(uid)) {
            throw new BaiBlogException(BizCodeEnumException.INNER_INPUT_EXCEPTION);
        }
        Long size = redisTemplate.opsForZSet().size(CacheConstants.getUserFansListKey(uid));
        if (size != null) {
            //缓存命中
            return size.intValue();
        } else {
            //缓存失效 包括了设置缓存的逻辑
            return this.getFansList(uid).size();
        }
    }


}
